using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Office = Microsoft.Office.Core;
using Excel = Microsoft.Office.Interop.Excel;
using System.Xml;
using System.Collections;
using System.IO.Packaging;

namespace LogAnalyzerIIS.LogClasses
{
	/// <summary>
	/// Loads W3C extended format logs 
	///</summary>
	public class W3CExtendedLogConverter : LogConverter
	{

		/// <summary>
		///   Returns XMLDocument filled with data for Worksheet part of the package
		/// </summary>
		/// <param name="sharedStringsSearch">
		///   Empty dictionary, will be filled with data for Shared strings part.
		/// </param>
		/// <param name="sharedStringsOutput">
		///   Empty list, will be filled with data for Shared strings part.
		/// </param>
		/// <param name="inputFilePath">
		///   Path to the input log file.
		/// </param>
		protected override XmlDocument CreateWorksheetXML(List<string> sharedStringsOutput, string inputFilePath)
		{
			// for searching, the string is what we search for so it is the key
			// the int is the index which gets returned from a search
			Dictionary<string, int> sharedStringsSearch = new Dictionary<string, int>();

			XmlElement sheetData = PrepareWorksheetXML(sharedStringsOutput, sharedStringsSearch);

			// reading input file
			StreamReader inputFileReader = new StreamReader(inputFilePath);
			int[] columnMappings = new int[0];

			// Excel starts rows at index 1
			// (Row 1 is the column headers)
			int currentExcelRowIndex = 2;

			while (!inputFileReader.EndOfStream)
			{
				string inputLine = inputFileReader.ReadLine().Trim();

				// check that the line has text
				if (inputLine.Length > 0)
				{
					// if the first character of the line is a # then it is a comment
					// otherwise we ignore the line
					if (inputLine[0] == '#')
					{
						// if row has declaration of columns / fields, then get the assign them to output columns 
						// this can be done multiple times through a single file
						if (inputLine.StartsWith("#Fields"))
							columnMappings = CreateColumnMappings(inputLine);
					}
					// the line is a row of data
					else
					{
						// step 1, split the line up into fields
						string[] inputLineValues = inputLine.Split(new char[] { ' ', '\t' });

						// step 2, add the to the output array using the order specified in the column mappings
						// (the output array is the same size and order of the output columns)
						string[] outputValues = new string[outputColumns.Count];
						for (int i = 0; i < outputColumns.Count; i++)
						{
							if (columnMappings[i] > -1)
								outputValues[i] = inputLineValues[columnMappings[i]];
							else
								outputValues[i] = "";
						}

						WriteRow(sheetData, currentExcelRowIndex, outputValues, sharedStringsSearch, sharedStringsOutput);

						// move on the next row
						currentExcelRowIndex++;
					}
				}
			}

			sharedStringsSearch.Clear();
			inputFileReader.Close();

			return worksheetXML;
		}

		/// <summary>
		///   Create column mappings by the W3C Extended log header row
		/// </summary>
		/// <param name="inputLine">
		///   Header row
		/// </param>
		private int[] CreateColumnMappings(string inputLine)
		{
			// split the fields up
			string[] inputLineFields = inputLine.Split(new char[] { ' ', '\t' });

			// initialize the column mapping array
			int[] outputColumnMappings = new int[outputColumns.Count];

			for (int i = 0; i < outputColumns.Count; i++)
				outputColumnMappings[i] = -1;

			// insert the index where the field can be found into the output column mappings
			for (int i = 1; i < inputLineFields.Length; i++)
			{
				switch (inputLineFields[i])
				{
					case "date":
						outputColumnMappings[0] = i - 1;
						break;
					case "time":
						outputColumnMappings[1] = i - 1;
						break;
					case "c-ip":
						outputColumnMappings[2] = i - 1;
						break;
					case "cs-username":
						outputColumnMappings[3] = i - 1;
						break;
					case "s-sitename":
						outputColumnMappings[4] = i - 1;
						break;
					case "s-computername":
						outputColumnMappings[5] = i - 1;
						break;
					case "s-ip":
						outputColumnMappings[6] = i - 1;
						break;
					case "s-port":
						outputColumnMappings[7] = i - 1;
						break;
					case "cs-method":
						outputColumnMappings[8] = i - 1;
						break;
					case "cs-uri-stem":
						outputColumnMappings[9] = i - 1;
						break;
					case "cs-uri-query":
						outputColumnMappings[10] = i - 1;
						break;
					case "sc-status":
						outputColumnMappings[11] = i - 1;
						break;
					case "sc-win32-status":
						outputColumnMappings[12] = i - 1;
						break;
					case "sc-bytes":
						outputColumnMappings[13] = i - 1;
						break;
					case "cs-bytes":
						outputColumnMappings[14] = i - 1;
						break;
					case "time-taken":
						outputColumnMappings[15] = i - 1;
						break;
					case "cs-version":
						outputColumnMappings[16] = i - 1;
						break;
					case "cs-host":
						outputColumnMappings[17] = i - 1;
						break;
					case "cs(User-Agent)":
						outputColumnMappings[18] = i - 1;
						break;
					case "cs(Cookie)":
						outputColumnMappings[19] = i - 1;
						break;
					case "cs(Referer)":
						outputColumnMappings[20] = i - 1;
						break;
					default:
						// unknown column
						outputColumnMappings[22] = i - 1;
						break;
				}
			}

			return outputColumnMappings;
		}
	}
}
